/********************************************************************************
* @file    ls_key.c
* @author  jianqiang.xue
* @version V2.0.0
* @date    2023-05-17
* @brief   注册式按键扫描 单键扫描/矩阵扫描
********************************************************************************/
/* Includes ------------------------------------------------------------------*/
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>

#include "ls_gpio.h"
#include "ls_syscfg.h"

#include "os_api.h"
#include "log.h"
#include "bsp_gpio.h"

#include "ls_key.h"

#include "app_io.h"
/* Private Includes ----------------------------------------------------------*/

/* Private Define ------------------------------------------------------------*/
#ifndef LS_APP_IO_CHANGE_SUPPORT
#define IO_TYPE(io)  (g_io_cfg[io].type)
#else // 支持可变
#define IO_TYPE(io)  (g_io_cfg[io].type)
#endif
/* Private Enum --------------------------------------------------------------*/
typedef enum {  // 按下状态
    KEY_NOT_PRESS = 0,
    KEY_ONE_PRESS,
    KEY_TWO_PRESS,
    KEY_LONG_PRESS,
    KEY_LONG_LONG_PRESS,
} key_cnt_t;
/* Private Struct ------------------------------------------------------------*/
// 定义按键状态结构体，用于记录按键的按下/抬起时间
typedef struct __attribute__((packed, aligned(1))) {
    uint8_t         io;           // io号
    ls_key_cb       cb;           // 事件回调

    uint8_t         level   : 1;  // 电平有效状态，比如低电平点亮led，则level设置0，反之。
    uint8_t         io_ste  : 1;  // 0--未按下  1--按下
    key_event_t     event   : 4;  // 按键事件类型
    uint8_t         reserve : 2;  // 预留

    key_cnt_t       press_cnt;    // 按下次数
    uint16_t        press_time;   // 按下时间
    uint16_t        release_time; // 释放时间

    void            *next;        // 下一个按键地址
} key_info_t;

/* Private Macro -------------------------------------------------------------*/
/* Private Variables ---------------------------------------------------------*/
static uint8_t g_key_num = 0;
static key_info_t *g_key_info = NULL; // 动态数组
static ls_key_cb g_key_event_cb = NULL;
/****************软定时器创建****************/
OS_TIMER_DECLARE_N(key_scan);

/* Extern Variables ---------------------------------------------------------*/

/* Private Function Prototypes ----------------------------------------------*/

/**
 * @brief  [子函数][软定时器回调]按键周期扫描,判断是否存在事件发生。支持同时扫描多个按键。
 * @retval true有事件发送
 */
static bool ls_key_scan(void) {
    uint8_t sign = 0;
    for (uint8_t i = 0; i < g_key_num; i++) {
        if (get_io_ste(g_key_info[i].io) == g_key_info[i].level) {
            g_key_info[i].press_time++;
            g_key_info[i].release_time = 0;
            g_key_info[i].event = KEY_EVENT_RELEASE;
            if (g_key_info[i].press_time >= 0xFFFD)
                g_key_info[i].press_time = 0xFFFD;

            if (g_key_info[i].io_ste == KEY_EVENT_NULL) {
                if (g_key_info[i].press_cnt == KEY_NOT_PRESS) {
                    g_key_info[i].press_cnt = KEY_ONE_PRESS;
                    g_key_info[i].event     = KEY_EVENT_RELEASE;
                    continue;
                } else if (g_key_info[i].press_cnt == KEY_ONE_PRESS &&
                        g_key_info[i].press_time >= (LS_BUTTON_SINGLE_CLICK_TIME / LS_BUTTON_SCAN_CYCLE_MS) &&
                        g_key_info[i].press_time < (LS_BUTTON_LONG_PRESS_TIME / LS_BUTTON_SCAN_CYCLE_MS)) {
                    g_key_info[i].press_cnt  = KEY_TWO_PRESS;
                    g_key_info[i].event      = KEY_EVENT_TWO_PRESS;
                    g_key_info[i].press_time = 0;
                    continue;
                }
            }
            g_key_info[i].event = KEY_EVENT_RELEASE;
            g_key_info[i].io_ste = KEY_EVENT_RELEASE;
            if (g_key_info[i].press_time == (LS_BUTTON_LONG_PRESS_TIME / LS_BUTTON_SCAN_CYCLE_MS) && g_key_info[i].press_cnt == KEY_ONE_PRESS) {
                g_key_info[i].event     = KEY_EVENT_LONG_PRESS;
                g_key_info[i].press_cnt = KEY_LONG_PRESS;
                sign = 2;
                g_io.func[i].io_event = IO_EVENT_IO_KEY_LONG_PRESS;
                continue;
            } else if (g_key_info[i].press_time == (LS_BUTTON_LONG_LONG_PRESS_TIME / LS_BUTTON_SCAN_CYCLE_MS) &&
                      g_key_info[i].press_cnt == KEY_LONG_PRESS) {
                g_key_info[i].event     = KEY_EVENT_LONG_LONG_PRESS;
                g_key_info[i].press_cnt = KEY_LONG_LONG_PRESS;
                sign = 3;
                g_io.func[i].io_event = IO_EVENT_IO_KEY_LONG_LONG_PRESS;
                continue;
            }
        } else {
            g_key_info[i].release_time++;
            if (g_key_info[i].press_cnt == KEY_ONE_PRESS &&
                g_key_info[i].release_time == (LS_BUTTON_RELEASE_TIME / LS_BUTTON_SCAN_CYCLE_MS)) {
                g_key_info[i].press_time = 0;
                g_key_info[i].event      = KEY_EVENT_SINGLE_CLICK;
                sign = 1;
                g_io.func[i].io_event = IO_EVENT_IO_KEY_SINGLE_CLICK;
                continue;
            } else if (g_key_info[i].press_cnt == KEY_TWO_PRESS) {
                if (g_key_info[i].release_time >= (LS_BUTTON_DOUBLE_CLICK_TIME / LS_BUTTON_SCAN_CYCLE_MS)) {
                    g_key_info[i].press_time = 0;
                    g_key_info[i].press_cnt = KEY_ONE_PRESS;
                    g_key_info[i].event     = KEY_EVENT_DOUBLE_CLICK;
                    sign = 1;
                    g_io.func[i].io_event = IO_EVENT_IO_KEY_TWO_CLICK;
                    continue;
                }
            } else if (g_key_info[i].release_time > (LS_BUTTON_RELEASE_TIME / LS_BUTTON_SCAN_CYCLE_MS)) {
                g_key_info[i].press_time = 0;
                g_key_info[i].press_cnt = KEY_NOT_PRESS;
                g_key_info[i].event     = KEY_EVENT_NULL;
            }

            g_key_info[i].io_ste = KEY_EVENT_NULL;
            if (g_key_info[i].release_time == 1 && g_key_info[i].press_cnt > 0) {
                g_key_info[i].event = KEY_EVENT_NULL;
                continue;
            }

            if (g_key_info[i].release_time > 0xFFFD)
                g_key_info[i].release_time = 0xFFFD;
        }
    }

    return sign > 0 ? 1 : 0;
}
/**
 * @brief  [软定时器回调] 按键扫描
 */
static void timer_key_scan_cb(void const *arg) {
    if (ls_key_scan()) { // 如果存在事件发生，则依次执行事件。
        for (uint8_t i = 0; i < g_key_num; i++) {
            switch (g_key_info[i].event) {
                case KEY_EVENT_RELEASE:
                    g_key_info[i].cb(KEY_EVENT_RELEASE);
                    break;
                case KEY_EVENT_PRESS:
                    g_key_info[i].cb(KEY_EVENT_PRESS);
                    break;
                case KEY_EVENT_TWO_PRESS:
                    g_key_info[i].cb(KEY_EVENT_TWO_PRESS);
                    g_key_info[i].cb(KEY_EVENT_PRESS);
                    // LOGD("k_scan [%d] KEY_EVENT_TWO_PRESS\r\n", i);
                    break;
                case KEY_EVENT_SINGLE_CLICK:
                    g_key_info[i].cb(KEY_EVENT_SINGLE_CLICK);
                    // LOGD("k_scan [%d] KEY_EVENT_SINGLE_CLICK", i);
                    break;
                case KEY_EVENT_DOUBLE_CLICK:
                    g_key_info[i].cb(KEY_EVENT_DOUBLE_CLICK);
                    // LOGD("k_scan [%d] KEY_EVENT_DOUBLE_CLICK\r\n", i);
                    break;
                case KEY_EVENT_LONG_PRESS:
                    g_key_info[i].cb(KEY_EVENT_LONG_PRESS);
                    // LOGD("k_scan [%d] KEY_EVENT_LONG_PRESS\r\n", i);
                    break;
                case KEY_EVENT_LONG_LONG_PRESS:
                    g_key_info[i].cb(KEY_EVENT_LONG_LONG_PRESS);
//                    LOGD("k_scan [%d] KEY_EVENT_LONG_LONG_PRESS\r\n", i);
                    break;
                case KEY_EVENT_NULL:
                    break;
                default:
                    break;
            }
        }
        if (g_key_event_cb)
            g_key_event_cb(0);
    }
}

/* Public Function Prototypes ------------------------------------------------*/

/**
 * @brief  注册一个按键回调
 * @param  io: 引脚号
 * @param  cb: 回调函数
 * @retval 0--成功  1--IO错误  2--IO不支持
 */
uint8_t ls_key_reg(uint8_t io, ls_key_cb cb) {
    key_info_t *p = NULL;
    key_info_t *key_info = g_key_info;
    uint8_t ret = 0;

    if (io == 0 || io > LS_IO_NUM - 1) return 1;

    if (IO_TYPE(io) != IO_TYPE_KEY) return 2;

    if (g_key_num == 0) goto create;
    else {
        for (uint8_t i = 0; i < g_key_num; i++) {
            if (key_info->io == io) goto succeed;
            else {
                // 找到末尾项
                if (key_info->next)
                    key_info = (key_info_t *)key_info->next;
                else
                    goto create;
            }
        }
    }
create: // 没有找到按键列表，创建新按键
    p = malloc(sizeof(key_info_t));
    if (!p) {
        ret = 1;
        goto end;
    } else {
        memset(p, 0, sizeof(key_info_t));
        p->io = io;
        p->cb = cb;
        if (g_key_num == 0) {
            g_key_info = p;
        } else {
            key_info->next = (key_info_t *)p;
            key_info       = (key_info_t *)p;
        }
        g_key_num++;
        goto succeed;
    }
succeed:
    ret = 0;
end:
    return ret;
}

/**
 * @brief  按键初始化，创建软定时器，按10ms进行扫描。有事件则触发cb回调。
 * @note   如果定义了回调数组，则优先执行回调数组。
 * @param  *event_cb: 任意事件回调
 * @retval  0--成功 1--失败
 */
uint8_t ls_key_init(void *event_cb) {
    if (g_key_num == 0) return false;
    uint32_t timer_ret = 0;
    // 创建软定时器 IO任务，用于定时执行任务或者写入flash。
    OS_TIMER_CREATE(key_scan, OS_TIMER_PERIODIC);
    g_key_event_cb = event_cb;
    return true;
}

void ls_key_deinit(void) {
    key_info_t *key_info = g_key_info;
    OS_TIMER_STOP(key_scan);
    OS_TIMER_DEL(key_scan);
    if (g_key_info) {
        for (uint8_t i = 0; i < g_key_num; i++) {
            void *temp = key_info->next;
            free(key_info);
            g_key_info = (key_info_t *)temp;
            g_key_num--;
            if (g_key_info) {
                key_info = (key_info_t *)key_info->next;
            } else
                break;
        }
        g_key_info = NULL;
    }
    g_key_num = 0;
}

/**
 * @brief  启动软定时器扫描按键
 */
inline void ls_key_start_scan(void) {
    OS_TIMER_START(key_scan, LS_BUTTON_SCAN_CYCLE_MS);
}

/**
 * @brief  停止软定时器扫描按键
 */
inline void ls_key_stop_scan(void) {
    OS_TIMER_STOP(key_scan);
}
